package conMgr.data;

import java.io.File;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import conMgr.prog.ErrorListener;
import conMgr.prog.ErrorProvider;

/**
 * The base class of an XML-based storage provider.
 * @author is4@uw.edu
 */
public abstract class AbstractXmlDataStore
	extends ErrorProvider
	implements DataStore, ErrorListener
{
	/**
	 * The XML file to read and write from.
	 */
	private final File file;
	
	/**
	 * The XML manager to use for getting elements and attributes.
	 */
	protected final XmlManager xml;
	
	/**
	 * Initializes a new instance of an abstract data store.
	 * @param file The XML file to read and write from.
	 */
	protected AbstractXmlDataStore(final File file)
	{
		this.file = file;
		xml = new XmlManager();
	}
	
	/**
	 * Registers a new error listener. Overridden to add the error listener to
	 * child classes.
	 */
	@Override
	public void addErrorListener(final ErrorListener errorListener)
	{
		super.addErrorListener(errorListener);
		xml.addErrorListener(errorListener);
	}
	
	/**
	 * Notifies the listener that an error has occurred. This implementation
	 * forwards the notification onto the parent error provider.
	 * @param description The description of the error.
	 * @param e The exception associated with this error, if any.
	 */
	public void errorOccurred(final String description, final Exception e)
	{
		error(description, e);
	}
	
	/**
	 * Gets an XML document object.
	 * @return An XML document object from the data store's file.
	 */
	protected Document getDocument()
	{
		final DocumentBuilderFactory factory =
			DocumentBuilderFactory.newInstance();
		DocumentBuilder builder;
		Document document;
		
		try
		{
			builder = factory.newDocumentBuilder();
			
			document = builder.parse(file);
			document.getDocumentElement().normalize();
			return document;
		}
		catch (final ParserConfigurationException e)
		{
			error("Error occurred while initializing XML support.", e);
		}
		catch (SAXException | IOException e)
		{
			error("Error occurred while reading " + file.getPath() + ".", e);
		}
		
		return null;
	}
	
	/**
	 * Creates a new XML document.
	 * @return A new XML document.
	 */
	protected Document newDocument()
	{
		final DocumentBuilderFactory factory =
			DocumentBuilderFactory.newInstance();
		DocumentBuilder builder;
		
		try
		{
			builder = factory.newDocumentBuilder();
		}
		catch (final ParserConfigurationException e)
		{
			error("Error occurred while initializing XML support.", e);
			return null;
		}
		
		return builder.newDocument();
	}
	
	protected boolean persist(final Document document)
	{
		final TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer;
		
		try
		{
			transformer = factory.newTransformer();
		}
		catch (final TransformerConfigurationException e)
		{
			error("Error occurred while initializing XML writing support.", e);
			return false;
		}
		
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		
		final DOMSource source = new DOMSource(document);
		final StreamResult result = new StreamResult(file);
		
		try
		{
			transformer.transform(source, result);
		}
		catch (final TransformerException e)
		{
			error("Error occurred while writing " + file.getPath() + ".", e);
			return false;
		}
		
		return true;
	}
}
